package org.oimwrapper.connectors.adapters;

import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;

/**
 * Establishes a basic SOAP connection to execute simple web services.
 */
public class OIMSoapClient {
	
	/**
	 * The document object representing the SOAP message.
	 */
	protected Document document;

	/**
	 * A reference to the body element in the SOAP message.
	 */
	protected Element body;
	
	/**
	 * The URL string for the SOAPbody schema definition.
	 */
	protected String xsd;

	/**
	 * The URL to connect to the web service.
	 */
	protected String url;
	
	/**
	 * The timeout to use for the remote server.
	 */
	protected int timeout;
	
	/**
	 * Default Constructor
	 *
	 * @param Url The URL to connect to the web service.
	 * @param Service The name of the web service being called.
	 * @exception Exception 
	 */
	public OIMSoapClient(String Url, String Service) throws Exception {
		url = Url;
		xsd = "urn:" + Service + "/wsdl";
		document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
		Element envelope = document.createElementNS("http://schemas.xmlsoap.org/soap/envelope/", "Envelope");
		envelope.setPrefix("soap");
		envelope.setAttribute("xmlns:ns1", xsd);
		envelope.setAttribute("xmlns:soap", "http://schemas.xmlsoap.org/soap/envelope/");
		document.appendChild(envelope);
		body = document.createElementNS("http://schemas.xmlsoap.org/soap/envelope/", "Body");
		body.setPrefix("soap");
		envelope.appendChild(body);
		timeout = 60000;
	}
	
	/**
	 * Sets the read/response timeout for the SOAP call.
	 * 
	 * @param Milliseconds The number of milliseonds to timeout after.
	 */
	public void setTimeout(int Milliseconds) {
		timeout = Milliseconds;
	}

	/**
	 * Processes a SOAP call.
	 *
	 * @param Endpoint The name of the endpoint method to call.
	 * @return The XML response from the SOAP call.
	 * @exception Exception 
	 */
	protected String processRequest(String Endpoint) throws Exception {
		StringBuffer result = new StringBuffer();
		boolean error = false;
		System.setProperty("sun.net.client.defaultReadTimeout", Integer.toString(timeout));
		HttpURLConnection connection = (HttpURLConnection) (new URL(url)).openConnection();
		try {
			Method setTimeoutMethod = connection.getClass().getDeclaredMethod("setReadTimeout", new Class[] {int.class});
			setTimeoutMethod.invoke(connection, new Object[] {new Integer(timeout)});
		} catch (Exception e) { ; }
		try {
			connection.setRequestProperty("Content-Type", "text/xml");
			connection.setRequestProperty("SOAPAction", url);
			connection.setDoInput(true);
			connection.setDoOutput(true);
			connection.setDefaultUseCaches(false);
			connection.setUseCaches(false);
			connection.setRequestMethod("POST");
			OutputStream request = connection.getOutputStream();
			try {
				Source source = new DOMSource(document);
				Result output = new StreamResult(request);
				Transformer transformer = TransformerFactory.newInstance().newTransformer();
				transformer.transform(source, output);
			} finally {
				request.close();
			}
			InputStream response = null;
			if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
				response = connection.getInputStream();
			} else {
				response = connection.getErrorStream();
				error = true;
			}
			try {
				BufferedReader reader = new BufferedReader(new InputStreamReader(response, "UTF-8"));
				while (true) {
					String line = reader.readLine();
					if (line == null) {
						break;
					}
					result.append(line);
				}
			} finally {
				response.close();
			}
		} finally {
			connection.disconnect();
		}
		if (error) {
			try {
				String status = getStatus(result.toString(), Endpoint);
				throw new Exception("Status: " + status + "; Message: " + result.toString());
			} catch (Exception e) {
				throw new Exception("Message: " + result.toString());
			}
		}
		return result.toString();
	}

	/**
	 * Parses the resulting status from the XML response for a SOAP call.
	 * @param Xml The response from the SOAP call to process.
	 * @param Endpoint The name of the endpoint method to call.
	 * @return Either the result value if the method succeeded, <code>FAILURE</code> if the method
	 *         failed without any specified error, or the specific error string if returned.
	 * @exception Exception
	 */
	protected String getStatus(String Xml, String Endpoint) throws Exception {
		String status = "FAILURE";
		try {
			NodeList nodes = getNodes("result", Xml);
			Node node = nodes.item(0);
			if (node != null) {
				try {
					status = ((Element) node).getChildNodes().item(0).getNodeValue();
				} catch (Exception e) {
					status = "";
				}
			} else {
				nodes = getNodes("faultstring", Xml);
				try {
					status = nodes.item(0).getFirstChild().getNodeValue();
				} catch (Exception e) { ; }
			}
		} catch (Exception e) {
			throw new Exception("Message: " + Xml, e);
		}
		return status;
	}

	/**
	 * Gets a list of nodes matching the specified name in the XML message.
	 *
	 * @param NodeName 
	 * @param Xml The SOAP message to process.
	 * @return A list of nodes matching the specified tag name,
	 * @exception Exception
	 */
	protected NodeList getNodes(String NodeName, String Xml) throws Exception {
		DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		Document doc = builder.parse(new ByteArrayInputStream(Xml.getBytes()));
		doc.normalize();
		return doc.getDocumentElement().getElementsByTagName(NodeName);
	}

	/**
	 * Executes a simple SOAP call.
	 *
	 * @param Endpoint The name of SOAP endpoint to execute.
	 * @param Names An array of names of attributes for the method.
	 * @param Values An array of values for the method, to in the same order as the names
	 * @return Either <code>SUCCESS</code> if the method succeeded, <code>FAILURE</code> if the method
	 *         failed without any specified error, or the specific error string if returned.
	 * @exception Exception 
	 */
	public String execute(String Endpoint, String[] Names, String[] Values) throws Exception {
		Node method = document.importNode(document.createElementNS(xsd, Endpoint), true);
		method.setPrefix("ns1");
		for (int i = 0; i < Names.length && i < Values.length; i++) {
			Element param = document.createElement(Names[i]);
			param.appendChild(document.createTextNode(Values[i]));
			method.appendChild(param);
		}
		body.appendChild(method);
		return getStatus(processRequest(Endpoint), Endpoint);
	}
}